var v = new Object();
var phi = (Math.sqrt(5) + 1) / 2;

var cartwheels;
var fresh_cartwheels;
var fresh_tiles;
var seed;
var seed_a;
var seed_d;

var p3_verts_found;
var p3_retry;
var p3_verts;
var p3_edges;
var p3_tiles;

var p2_verts;
var p2_edges;
var p2_tiles;


var valid = [];

var bars;
var ints;
var drawn;

var expand;
var tangents;
var cartwheel_r;
var star_r;
var long_d;
var arc_d;

var p3_l;

var draw_tiles;

var opts;
var opt_labels;

var thickness = 1;

var auto_update = true;
var options_visible = false;
var auto_timeout = null;
var options_timeout = null;

var clicked_down_callback = null;

var download_ready = false;
var is_drawing = true;

var fixed = null;

// copied from old "query.js"
var query;

function query_init() {
	query = new Array();
	process.argv.forEach(function(val, index, array) {
		if (index < 2) {return;}
		var p = val.split('=');
		var k = decodeURIComponent(p[0]);
		query[k] = decodeURIComponent([p[1]]);
	});
}
// end old "query.js"

// copied from old "canvas.js"

var ctx;
var canvas;

function new_draw_string() {
	return {
		str: ' ',
		clr: 'white',
		pnt: '12',
		x: 0,
		y: 0,
		align: 'left'
	}
}

function canvas_clear() {
	console_log("canvas_clear");
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	set_fill_style("rgb(0,0,0)");
	ctx.fillRect(0, 0, canvas.width, canvas.height);
}

function canvas_save() {
	//window.open(canvas.toDataURL('image/png'));
}

function canvas_init() {
	//var Canvas = require('canvas');

	//canvas = new Canvas(fixed.w, fixed.h);
	//ctx = canvas.getContext('2d');
	canvas = new Object();
	canvas.width = 640;
	canvas.height = 480;

	ctx = new Object;

	ctx.clearRect = (function(l, t, w, h) {
		return;
		console_log("clear_rect " + 
			l + " " + t + " " + 
			w + " " + h);
	});

	ctx.fillRect = (function(l, t, w, h) {
		return;
		console_log("fill_rect " + 
			l + " " + t + " " + 
			w + " " + h);
	});

	ctx.save = (function() {
		console_log("save");
	});

	ctx.beginPath = (function() {
		console_log("begin_path");
	});

	ctx.arc = (function(x, y, r, s, e, c) {
		console_log("arc " + 
			x + " " + y + " " + r + " " + 
			s + " " + e + " " + c);
	});

	ctx.stroke = (function() {
		console_log("stroke");
	});

	ctx.moveTo = (function(x, y) {
		console_log("move_to " + x + " " + y);
	});

	ctx.lineTo = (function(x, y) {
		console_log("line_to " + x + " " + y);
	});

	ctx.restore = (function() {
		console_log("restore");
	});

	ctx.closePath = (function() {
		console_log("close_path");
	});

	ctx.fill = (function() {
		console_log("fill");
	});

	canvas_clear();
}

function new_draw_line() {
	return {
		clr: 'white',
		thick: '1.0',
		xa: 0,
		ya: 0,
		xb: 0,
		yb: 0
	}
}

function draw_line(l) {
	// l is an object with the following properties
	// l.clr line color
	// l.thick line thickness
	// l.xa point a
	// l.yb "
	// l.xa point b
	// l.yb "

	ctx.save();
	set_stroke_style(l.clr);
	set_line_cap("round");
	set_line_width(l.thick);
	ctx.beginPath();

	var xa = parseFloat('' + l.xa);
	var ya = parseFloat('' + l.ya);
	var xb = parseFloat('' + l.xb);
	var yb = parseFloat('' + l.yb);
	var thick = parseFloat('' + l.thick);

	ctx.moveTo(xa, ya);
	ctx.lineTo(xb, yb);
	ctx.stroke();

	ctx.restore();
}

function rainbow_numbers(r) {
	//r is between 0 and 1
	//we want a rainbow color

	var c = (r - Math.floor(r)) * 6;
	var d = Math.floor(c);
	//d %= 6; // should never happen
	var v = c - d;
	if (1 == d % 2) {
		v = 1 - v;
	}

	//alert (r + ":" + c + ":" + v);

	v *= 255;
	v = Math.floor(v);
	//v %= 256; // should never happen...

	var o = [];

	if (0 == d) {
		//red to yellow
		o = [255, v, 0];
	} else if (1 == d) {
		//yellow to green
		o = [v, 255, 0];
	} else if (2 == d) {
		//green to cyan
		o = [0, 255, v];
	} else if (3 == d) {
		//cyan to blue
		o = [0, v, 255];
	} else if (4 == d) {
		//blue to magenta
		o = [v, 0, 255];
	} else if (5 == d) {
		//magenta to red
		o = [255, 0, v];
	}

	//alert(r + ":" + o)
	return o;
}

function rainbow_color(r) {
	var o = rainbow_numbers(r);
	return "rgb(" + o.join(",") + ")";
}

function new_draw_dot() {
	return {
		x: "0.0",
		y: "0.0",
		r: "1.0",
		clr: "white"
	};
}

function draw_dot(d) {
	var x = parseFloat("" + d.x);
	var y = parseFloat("" + d.y);
	var r = parseFloat("" + d.r);
	var c = d.clr;

	ctx.save();
	set_fill_style(d.clr);

	ctx.beginPath();
	ctx.arc(x, y, r, 0, 2 * Math.PI, false);
	ctx.fill();

	ctx.restore();
}

function new_draw_dot_circle() {
	return {
		r: "0.0",
		dr: "1.0",
		x: "0.0",
		y: "0.0",
		count: "1",
		clr: "white"
	};
}

function draw_dot_circle_dot(c, t) {
	// x is radius * cos theta
	// y is radius * sin theta
	// but we're starting with theta at pi/2 (90)

	var theta = (t * 2 * Math.PI) - (Math.PI / 2);

	var d = new_draw_dot();
	d.r = c.dr;
	d.clr = c.clr;
	d.x = c.x + c.r * Math.cos(theta);
	d.y = c.y + c.r * Math.sin(theta);

	draw_dot(d);
	return { x: d.x, y: d.y };
}

function draw_dot_circle(c) {
	var d = new_draw_dot_circle();
	var a = new Array();
	d.x = parseFloat(c.x);
	d.y = parseFloat(c.y);
	d.r = parseFloat(c.r);
	d.dr = parseFloat(c.dr);
	d.count = parseInt(c.count);
	d.clr = c.clr;
	for (var i = 0; i < d.count; ++i) {
		a.push(draw_dot_circle_dot(d, i/d.count));
	}
	return a;
}

// end old "canvas.js"

function clear_globals() {
	init_tangents();
	cartwheels = [];
	seed = null;
	fresh_cartwheels = [];
	fresh_tiles = [];

	draw_tiles = [];

	p3_retry = [];
	p3_verts_found = [];
	p3_verts = [];
	p3_edges = [];
	p3_tiles = [];

	p2_verts = [];
	p2_edges = [];
	p2_tiles = [];

	bars = [];
	drawn = [];
	ints = [];
}

function set_line_cap(s) {
	ctx.lineCap = s;
	console_log("line_cap " + ctx.lineCap);
}

function set_stroke_style(s) {
	if (s == ctx.strokeStyle) {return;}
	ctx.strokeStyle = s;
	console_log("stroke_style " + ctx.strokeStyle);
}

function set_fill_style(s) {
	if (s == ctx.fillStyle) {return;}
	ctx.fillStyle = s;
	console_log("fill_style " + ctx.fillStyle);
}

function set_line_width(w) {
	if (w == ctx.lineWidth) {return;}
	ctx.lineWidth = cast_float(w) * cast_float(thickness);
	console_log("line_width " + ctx.lineWidth);
}

function init() {
	query_init();
	check_fixed();
	canvas_init();
	check_thickness();
	init_options();
	on_resize();
}

function check_thickness() {
	if ( null != query['line'] ) { thickness = query['line'] }
}

function cast_float(v) {
	var s = '' + v;
	var n = parseFloat(s);
	if(isNaN(n)) { return 0.0; }
	return n;
}

function cast_int(v) {
	var s = '' + v;
	var n = parseInt(s);
	if(isNaN(n)) { return 0; }
	return n;
}

function check_fixed() {
	error_log(typeof query['w']);
	if (
		(null != query['w']) && 
		(null != query['h'])
	) { 
		fixed = {
			w: cast_int(query['w']),
			h: cast_int(query['h'])
		};
	} else {
		fixed = {
			w: 640,
			h: 480
		};
	}
}

function console_clear() {
	return;
}

function status_clear() {
	return;
}

function status_update(s) {
	return;
}

function console_log(s) {
	console.log(s);
	return;
}

function error_log(s) {
	return;
	console.error(s);
}

function on_resize() {
	if (null == fixed) {
		canvas.width = 200
		canvas.height = 200
	} else {
		canvas.width = fixed.w;
		canvas.height = fixed.h;
	}
	ctx.globalCompositeOperation = 'lighter';
	set_viewport();
}

var middle_c;

function get_musical_distance(t) {
	var c = (phi + 1) / 2;
	if (0 == t) { return 1 - c; }
	var m = ((t < 0) ? -1 : 1);
	var i = Math.abs(t) - 2;
	middle_c_term(i + 3);
	var d = c * m;
	if (i < 0) { return d; }
	d += m * (middle_c.S[i] + middle_c.L[i] * phi);
	return d;
}

function expand_middle_c() {
	// l, s l, l s l, s l l s l,
	if (0 == middle_c.m.length) {
		middle_c.m = ['L', 'S', 'L'];
		middle_c.L = [1, 1, 2];
		middle_c.S = [0, 1, 1];
		middle_c.a = 0;
		middle_c.b = 1;
		return;
	}
	var b = middle_c.m.length;
	var i;
	var t;
	for (i = middle_c.a; i < b; ++i) {
		t = middle_c.m[i];
		middle_c.m.push(t);
		middle_c.L.push(middle_c.L[middle_c.L.length - 1]);
		middle_c.S.push(middle_c.S[middle_c.S.length - 1]);
		++middle_c[t][middle_c[t].length - 1];
	}
	middle_c.a = middle_c.b;
	middle_c.b = b;
}

function middle_c_term(n) {
	var t = n;
	if (0 == t) { return 'S'; }
	if (1 == t) { return 'L'; }
	if (t > 1) { t -= 2; }
	else { ++t; t *= -1; }
	if (null == middle_c) { middle_c = {
		m: [],
		L: [],
		S: [],
		a: 0,
		b: 0
	}}
	while (middle_c.m.length < t) {
		expand_middle_c();
	}
	return middle_c.m[t];
}

function view_scroll(x, y) {
	//how big is the screen?
	var hx =  v.d.w; //canvas.width / v.z;
	var hy =  v.d.h; //canvas.height / v.z;
	set_viewport(v.x + x * hx, v.y + y * hy, v.z);
}

function view_click(e) {
	auto_update = false;
	var hx = 1 - 2 * e.clientX / canvas.width;
	var hy = 1 - 2 * e.clientY / canvas.height;
	view_scroll(-hx, -hy);
}

function animate_tiles() {
	set_viewport(v.x, v.y, v.z);
}

function view_zoom(z) {
	set_viewport(v.x, v.y, v.z * Math.pow(phi, z));
}

function set_viewport(x, y, z) {
	status_clear();
	v.x = ((null == x) ? v.x : x);
	v.y = ((null == y) ? v.y : y);
	v.z = ((null == z) ? v.z : z);
	if (null == v.x) { v.x = 0; }
	if (null == v.y) { v.y = 0; }
	if (null == v.z) { v.z = 60; }

	// what are the edges of the viewport ?
	// z is pixels/unit
	var w = .5 * canvas.width / v.z;
	var h = .5 * canvas.height / v.z;

	v.l = [{x: v.x - w, y: v.y - h}, {x: v.x - w, y: v.y + h}];
	v.t = [{x: v.x - w, y: v.y - h}, {x: v.x + w, y: v.y - h}];
	v.r = [{x: v.x + w, y: v.y - h}, {x: v.x + w, y: v.y + h}];
	v.b = [{x: v.x - w, y: v.y + h}, {x: v.x + w, y: v.y + h}];

	v.d = new Object;
	v.d.w = w;
	v.d.h = h;
	v.d.l = v.x - v.d.w;
	v.d.t = v.y - v.d.h;
	v.d.r = v.x + v.d.w;
	v.d.b = v.y + v.d.h;

	v.cx = canvas.width / 2;
	v.cy = canvas.height / 2;

	redraw();
}

function valid_cartwheel_bar(g, n) { 
	var b = find_bar(g, n);
	// this is a valid bar if it has a long gap on one side
	// and a short gap on the other
	return b.cartwheel_valid; 
}

function valid_cartwheel(a) {
	var k = a.join(":");
	if (null != valid[k]) { return valid[k]; }
	for (var i in a) {
		if (! valid_cartwheel_bar(i, a[i])) { 
			valid[k] = false; 
			return false;
		}
	}
	valid[k] = true;
	return true;
}

function find_cartwheel(a) {
	var k = a.join(':');
	console_log("find_cartwheel_begin " + k);
	var o = cartwheels[k];
	if (null == o) {
		if (! valid_cartwheel(a)) { 
			console_log("no_cartwheel " + k);
			console_log("find_cartwheel_end " + k);
			return null; 
		}
		console_log("new_cartwheel " + k);
		o = {
			k: k,
			draw: 'cartwheel',
			a: a
		};
		cartwheels[k] = o;
		find_cartwheel_verts(o);
		fresh_cartwheels.push(o);
		fresh_tiles.push(o);
	}
	console_log("find_cartwheel_end " + k);
	return o;
}

function orient_cartwheel(c) {

	var o = 'o';
	var b = [];
	for (var i = 0; i < c.bars.length; ++i) {
		var l = Math.abs(c.bars[i][1].d - c.bars[i][0].d);
		var r = Math.abs(c.bars[i][1].d - c.bars[i][2].d);
		o += (l > r ? "0" : "1");
		b[i] = [];
		var m = (l > r);
		for (var j = 0; j < 3; ++j) {
			if (m) { b[i].push(c.bars[i][j]); }
			else { b[i].unshift(c.bars[i][j]); }
		}
	}
	var a = {
		o00000: 0,
		o11000: 1,
		o00001: 2,
		o01111: 3,
		o11100: 4,
		o11111: 5,
		o00111: 6,
		o11110: 7,
		o10000: 8,
		o00011: 9
	};
	var n = a[o];

	a = b;
	b = [];
	for (var i = n; i < n + 5; ++i) {
		b.push(a[(i + n) % 5]);
	}
	c.o = n;
	c.obars = b;
	return;

	var verts = [];
	verts.push(find_intersection(b[0][1], b[4][1]));
	verts.push(find_intersection(b[4][1], b[1][1]));
	verts.push(find_intersection(b[1][1], b[2][1]));
	verts.push(find_intersection(b[2][1], b[3][1]));
	verts.push(find_intersection(b[3][1], b[0][1]));

	for (var i in verts) {draw_quick_dot(verts[i]);}
	var sun = find_intersection(b[0][2], b[4][2]);
	set_line_width(2);
	set_stroke_style("gray");
	draw_segments(scale_points([sun, verts[0]]));
}

function get_center_bar(theta) {
	return get_bar(0, 0, theta);
}

function get_bar(x, y, theta) {
	return [{x: x, y:y}, {x: x + Math.cos(theta), y: y - Math.sin(theta)}]
}

function find_center_bar(g) {
	var theta = (1 + g * 2) * Math.PI / 5;
	return {k: g, theta: theta, bar: get_center_bar(theta)};
}

function find_bar(g, n) {
	var k = g + ":" + n;
	var o = bars[k];
	if (null == o) {
		var d = (0 == g % 2) ? 1 : -1;
		var c = find_center_bar(g);
		var md = d * get_musical_distance(n);
		var cv = Math.abs(
			get_musical_distance(n - 1) -
			get_musical_distance(n + 1)
		) < 3;
		var bar = find_parallel_line(c.bar, md);
		o = {
			k: k,
			g: g,
			i: n,
			bar: bar,
			theta: c.theta,
			cartwheel_valid: cv,
			d: md
		};
		o.color = rainbow_numbers(.5 + o.d / 12);
		if (opts['gray']) { o.color = [127, 127, 127]; }
		bars[k] = o;
	}
	return o;
}

function find_cartwheel_bars(c) {
	c.bars = [];
	c.color = [0, 0, 0];
	for (var i = 0; i < 5; ++i) {
		c.bars[i] = [
			find_bar(i, c.a[i] - 1),
			find_bar(i, c.a[i]),
			find_bar(i, c.a[i] + 1)
		];
		for (var j in c.bars[i][1].color) {
			c.color[j] += c.bars[i][1].color[j];
		}
	}
	for (var i in c.color) { 
		c.color[i] = Math.floor(c.color[i] / 5); 
	}
	orient_cartwheel(c);
	find_p3_verts(c);
	find_p3_edges(c);
	find_cartwheel_star(c);
}

function find_p3_edge(a) {
	set_stroke_style("rgb(15,15,15)");
	set_line_width(1);
	var k = [];
	for (var i in a) {k.push(a[i].k)}
	k.sort();
	k = 'p3_edge[' + k.join('][') + ']';
	var o = p3_edges[k];
	if (null == o) {
		o = {
		k: k,
		verts: [],
		tiles: []
		};
		p3_edges[k] = o;
		for (var i in a) {
			o.verts[a[i].k] = a[i];
			a[i].edges[k] = o;
		}
		if (null == p3_l) { 
			var p = [];
			for (var i in a) { p.push(a[i]) }
			p3_l = get_distance(p[0], p[1]);
		}

		//draw_segments(scale_points(a));
	}
	return o;
}

function find_p3_edges(c) {
	find_p3_edge([c.p3_verts['ots'], c.p3_verts['trp']])
	find_p3_edge([c.p3_verts['trp'], c.p3_verts['ord']])
	find_p3_edge([c.p3_verts['ord'], c.p3_verts['ors']])
	find_p3_edge([c.p3_verts['ors'], c.p3_verts['brp']])
	find_p3_edge([c.p3_verts['brp'], c.p3_verts['obd']])
	find_p3_edge([c.p3_verts['obd'], c.p3_verts['blp']])
	find_p3_edge([c.p3_verts['blp'], c.p3_verts['ols']])
	find_p3_edge([c.p3_verts['ols'], c.p3_verts['old']])
	find_p3_edge([c.p3_verts['old'], c.p3_verts['tlp']])
	find_p3_edge([c.p3_verts['tlp'], c.p3_verts['ots']])
	find_p3_edge([c.p3_verts['ots'], c.p3_verts['itp']])
	find_p3_edge([c.p3_verts['itp'], c.p3_verts['ird']])
	find_p3_edge([c.p3_verts['ird'], c.p3_verts['ors']])
	find_p3_edge([c.p3_verts['itp'], c.p3_verts['ild']])
	find_p3_edge([c.p3_verts['ild'], c.p3_verts['ols']])

	find_p3_edge([c.p3_verts['boat'], c.p3_verts['ild']])
	find_p3_edge([c.p3_verts['boat'], c.p3_verts['ird']])
	find_p3_edge([c.p3_verts['boat'], c.p3_verts['blp']])
	find_p3_edge([c.p3_verts['boat'], c.p3_verts['brp']])

	find_p3_edge([c.p3_verts['ldi'], c.p3_verts['tlp']])
	find_p3_edge([c.p3_verts['ldi'], c.p3_verts['ols']])
	find_p3_edge([c.p3_verts['ldi'], c.p3_verts['itp']])

	find_p3_edge([c.p3_verts['rdi'], c.p3_verts['trp']])
	find_p3_edge([c.p3_verts['rdi'], c.p3_verts['ors']])
	find_p3_edge([c.p3_verts['rdi'], c.p3_verts['itp']])
}

function draw_quick_dot(d, r, c) {
	var dot = new_draw_dot();
	dot.clr = ((null == c) ? "white" : c);
	dot.r = ((null == r) ? 2 : r);

	var p = scale_points([d])[0];
	dot.x = p.x;
	dot.y = p.y;
	draw_dot(dot);
}

function find_intersection(a, b) {
	var k = [a.k, b.k];
	k.sort();
	k = '[' + k.join('][') + ']';
	var o = ints[k];
	if (null == o) {
		o = get_intersection(a.bar, b.bar);
		o.bars = [];
		o.bars[a.k] = a;
		o.bars[b.k] = b;
		o.k = k;
		ints[k] = o;
	}
	return o;
}

function find_cartwheel_verts(c) {
	if (null == c.obars) { find_cartwheel_bars(c); }
	var b = c.obars;
	var verts = [];
	verts.push(find_intersection(b[0][0], b[1][0]));
	verts.push(find_intersection(b[3][0], b[4][0]));
	verts.push(find_intersection(b[1][2], b[2][2]));
	verts.push(find_intersection(b[4][2], b[0][0]));
	verts.push(find_intersection(b[2][0], b[3][0]));
	verts.push(find_intersection(b[0][2], b[1][2]));
	verts.push(find_intersection(b[3][2], b[4][2]));
	verts.push(find_intersection(b[1][0], b[2][0]));
	verts.push(find_intersection(b[4][0], b[0][2]));
	verts.push(find_intersection(b[2][2], b[3][2]));

	c.vert = get_polygon_center(verts);
	c.verts = verts;
	find_cartwheel_inner_decagon(c);
	if (null == cartwheel_r) {
		cartwheel_r = 2 * get_distance(c.vert, c.p3_verts['star']);
		star_r = 2 * get_distance(c.p3_verts['boat'], c.p3_verts['star']);
	}
}

function find_cartwheel_inner_decagon(c) {
	var verts = [];
	var b = c.obars;
	verts.push(find_intersection(b[2][2], b[4][0]));
	verts.push(find_intersection(b[4][0], b[1][0]));
	verts.push(find_intersection(b[1][0], b[3][2]));
	verts.push(find_intersection(b[3][2], b[0][2]));
	verts.push(find_intersection(b[0][2], b[2][0]));
	verts.push(find_intersection(b[2][0], b[4][2]));
	verts.push(find_intersection(b[4][2], b[1][2]));
	verts.push(find_intersection(b[1][2], b[3][0]));
	verts.push(find_intersection(b[3][0], b[0][0]));
	verts.push(find_intersection(b[0][0], b[2][2]));

	c.decagon = verts;
}

function find_next_bar(a, b) {
	var d = b.i - a.i;
	return find_bar(a.g, b.i + d);
}

function find_p2_tiles(t) {
	// every wide p3 tile maps to a dart and two half-kites

	// we won't bother with narrow tiles, since they just
	// contain the other half of kites from wide tiles

	var retry = false;

	if (t.t) { return; }

	// we can find the dart first because it's easy

	find_p2_tile("dart", [t.verts[0], t.verts[1], t.dart_vert, t.verts[3]]);

	var ot = find_neighbor_tile(t, find_p3_edge([t.verts[2], t.verts[1]]));
	if (null != ot) { 
		var ov = ot.t ? ot.verts[0] : ot.dart_vert;
		find_p2_tile("kite", [t.verts[2], t.dart_vert, t.verts[1], ov]);
	} else { retry = true; }

	ot = find_neighbor_tile(t, find_p3_edge([t.verts[2], t.verts[3]]));
	if (null != ot) { 
		var ov = ot.t ? ot.verts[0] : ot.dart_vert;
		find_p2_tile("kite", [t.verts[2], t.dart_vert, t.verts[3], ov]);
	} else { retry = true; }

	if (retry) {p3_retry[t.k] = t}

}

function find_neighbor_tile(t, edge) {
	for (var i in edge.tiles) {
		var ot = edge.tiles[i];
		if (ot.k != t.k) { return ot; }
	}
	return null;
}

function find_p2_vert(vert) {
	var o = p2_verts[vert.k];
	if (null == o) {
		o = {
			k: vert.k, 
			x: vert.x, 
			y: vert.y, 
			color: vert.color,
			t: vert.t, 
			edges: [],
			tiles: []
		};
		p2_verts[vert.k] = o;
	}
	return o;
}

function find_p2_edge(a, b) {
	var k = [a.k, b.k];
	k.sort();
	k = 'p2edge:' + k.join('x');

	var o = p2_edges[k];
	if (null == o) {
		o = {k: k, verts: [], tiles: []};

		o.verts[a.k] = a;
		a.edges[o.k] = o;

		o.verts[b.k] = b;
		b.edges[o.k] = o;

		p2_edges[k] = o;

		/*
		set_line_width(3);
		set_stroke_style("rgb(63,63,0)");
		draw_segments(scale_points([a, b]));
		*/
	}
	return o;
}

function find_p2_edges(t) {
	for (var i = 0; i < 4; ++i) {
		var e = find_p2_edge(t.verts[i], t.verts[(i + 1) % 4]);
		t.edges[e.k] = e;
		e.tiles[t.k] = t;
		e.t = (i == 1 || i == 2);
	}
}

function find_p2_verts(t, a) {
	for (var i = 0; i < a.length; ++i) {
		var vert = find_p2_vert(a[i]);
		t.verts.push(vert);
		vert.tiles[t.k] = t;
	}	
}
function find_p2_tile(t, a) {
	var k = [];

	for (i in a) {k.push(a[i].k);}
	k.sort();
	k = t + ':' + k.join(':');
	var o = p2_tiles[k];
	if (null == o) {
		o = { k: k, edges: [], verts: [], t: t};
		o.draw = 'p2_tile';
		find_p2_verts(o, a);
		find_p2_edges(o);
		find_tile_color(o);
		p2_tiles[k] = o;

		fresh_tiles.push(o);

		/*
		set_fill_style(
			('dart' == t) ? 
				"rgb(31,0,0)" : 
				"rgb(0,31,0)" 
		); 
		fill_polygon(scale_points(a));
		*/
	}
	return o;
}

function draw_p2_arcs(t) {
	if (! opts['arcs']) { return; }
	error_log("draw_p2_arcs");
	var th = get_theta([t.verts[0], t.verts[2]]);
		th += Math.PI;
		th = - th;
		th -= Math.PI

	var m;
	d = long_d;
	if ('kite' == t.t) {
		error_log("kite");
		m = 2;
		d = d / phi;
	} else {
		error_log("dart");
		m = 3;
		d = d * (1 - 1/phi);
	}


	var dth = Math.PI / 5;

	p = scale_points([t.verts[0]])[0];

	error_log(p);
	error_log(th);
	set_stroke_style(opts['p3'] ? "gray" : "rgb(63,63,63)");
	set_line_width(1);
	ctx.save();
	ctx.beginPath();
	ctx.arc(p.x, p.y, d * v.z, th - dth, th + dth, false);
	ctx.stroke();

	p = scale_points([t.verts[2]])[0];
	th += Math.PI;
	dth = m * Math.PI / 5;
	d /= phi;

	set_stroke_style(opts["p3"] ? "white" : "rgb(127,127,127)");
	set_line_width(1);
	ctx.save();
	ctx.beginPath();
	ctx.arc(p.x, p.y, d * v.z, th - dth, th + dth, false);
	ctx.stroke();


}

function draw_p2_tile(t) {
	if (! opts['p2']) { return; }
	error_log("draw_p2_tile");
	if (opts['edges'] || (!(
		opts['arcs'] || 
		opts['cartwheel'] || 
		opts['decagon'] || 
		opts['colors'] ||
		opts['edges']
	))) { 
		for (var i in t.edges) { draw_tile_edge(t.edges[i]); }
	}
	fill_tile(t);
	draw_p2_arcs(t);
}

function find_p3_verts(c) {
	var b = c.obars;
	c.p3_verts = [];
	var verts = [];
	//verts.push(find_intersection(b[2][0], b[4][2]));
	//verts.push(find_intersection(b[4][2], b[1][2]));
	//verts.push(find_intersection(b[1][2], b[3][0]));
	//verts.push(find_intersection(b[3][0], b[0][0]));
	//verts.push(find_intersection(b[0][0], b[2][2]));
	//verts.push(find_intersection(b[2][2], b[4][0]));
	//verts.push(find_intersection(b[4][0], b[1][0]));
	//verts.push(find_intersection(b[1][0], b[3][2]));
	//verts.push(find_intersection(b[3][2], b[0][2]));
	//verts.push(find_intersection(b[0][2], b[2][0]));

	//for (var i in verts) {draw_quick_dot(verts[i]);}
	set_fill_style("gray");
	//fill_polygon(scale_points(verts));

	c.p3_verts['ird'] = 
		find_deuce_p3_vert([b[4][1], b[1][1], b[2][1]]);
	c.p3_verts['ild'] = 
		find_deuce_p3_vert([b[0][1], b[2][1], b[3][1]]);
	c.p3_verts['ord'] = 
		find_deuce_p3_vert([b[1][2], b[3][0], b[4][2]]);
	c.p3_verts['obd'] = 
		find_deuce_p3_vert([b[2][2], b[4][0], b[0][0]]);
	c.p3_verts['old'] = 
		find_deuce_p3_vert([b[1][0], b[3][2], b[0][2]]);
	c.p3_verts['ots'] = find_sun_p3_vert([
		b[0][2], b[1][1], b[2][0], b[3][1], b[4][2]
	]);
	c.p3_verts['ors'] = find_sun_p3_vert([
		b[0][0], b[1][2], b[2][1], b[3][0], b[4][1]
	]);
	c.p3_verts['ols'] = find_sun_p3_vert([
		b[0][1], b[1][0], b[2][1], b[3][2], b[4][0]
	]);
	c.p3_verts['itp'] = find_point_p3_vert([
		b[0][1], b[4][1], b[3][1], b[1][1]
	]);
	c.p3_verts['trp'] = find_point_p3_vert([
		b[0][1], b[1][2], b[2][0], b[4][2]
	]);
	c.p3_verts['brp'] = find_point_p3_vert([
		b[1][1], b[2][2], b[3][0], b[0][0]
	]);
	c.p3_verts['blp'] = find_point_p3_vert([
		b[3][1], b[2][2], b[1][0], b[4][0]
	]);
	c.p3_verts['tlp'] = find_point_p3_vert([
		b[4][1], b[3][2], b[2][0], b[0][2]
	]);
	c.p3_verts['boat'] = find_boat_p3_vert([
		b[0][0], b[1][1], b[2][1],
		b[3][1], b[4][0], b[2][2]
	]);
	c.p3_verts['rdi'] = find_diamond_p3_vert([
		b[4][2], b[0][1], b[1][1],
		b[4][1], b[2][1], b[3][0], b[1][2]
	]);
	c.p3_verts['ldi'] = find_diamond_p3_vert([
		b[0][2], b[4][1], b[3][1],
		b[0][1], b[2][1], b[1][0], b[3][2]
	]);
}

function find_deuce_p3_vert(b) { 
	var k = [];
	for (var i in b) {k.push(b[i].k)}
	k.sort();
	k = "deuce|" + k.join("|");
	var o = p3_verts[k];
	if (null == o) {
		o = {k: k, bars: b, verts: [], edges: [], tiles: []};
		o.draw = 'p3_vert'; o.p1 = 'deuce';
		p3_verts[k] = o;
		o.verts.push(find_intersection(b[1], b[2]));
		o.verts.push(find_intersection(b[2], b[0]));
		o.verts.push(find_intersection(b[0], b[1]));

		o.vert = get_intersection(
		get_bar(
			o.verts[1].x, o.verts[1].y,
			(Math.PI + b[2].theta + b[0].theta) / 2
		),
		get_bar(
			o.verts[2].x, o.verts[2].y,
			(Math.PI + b[1].theta + b[0].theta) / 2
		)
		);

		o.x = o.vert.x;
		o.y = o.vert.y;
		o.color = find_color(b);

		set_fill_style("gray");
		set_stroke_style("gray");
		//fill_polygon(scale_points(o.verts));
		//draw_quick_dot(o, 1, "gray");
		fresh_tiles.push(o);
	}
	return o;
}

function find_point_p3_vert(b) {
	var k = [];
	for (var i in b) {k.push(b[i].k)}
	k.sort();
	k = "point|" + k.join("|");
	var o = p3_verts[k];
	if (null == o) {
		o = {k: k, bars: b, verts: [], edges: [], tiles: []};
		o.draw = 'p3_vert'; o.p1 = 'point';
		p3_verts[k] = o;
		o.verts.push(find_intersection(b[0], b[1]));
		o.verts.push(find_intersection(b[1], b[2]));
		o.verts.push(find_intersection(b[2], b[3]));
		o.verts.push(find_intersection(b[3], b[0]));

		o.vert = get_intersection(
		get_bar(
			o.verts[0].x, o.verts[0].y,
			(Math.PI + b[0].theta + b[1].theta) / 2
		),
		get_bar(
			o.verts[3].x, o.verts[3].y,
			(Math.PI + b[3].theta + b[0].theta) / 2
		)
		);
		o.x = o.vert.x;
		o.y = o.vert.y;
		o.color = find_color(b);

		set_fill_style("gray");
		//fill_polygon(scale_points(o.verts));
		//draw_quick_dot(o, 1, "gray");
		fresh_tiles.push(o);
	}
	return o;

}

function find_diamond_p3_vert(b) {
	var k = [];
	for (var i in b) {k.push(b[i].k)}
	k.sort();
	k = "diamond|" + k.join("|");
	var o = p3_verts[k];
	if (null == o) {
		o = {k: k, bars: b, verts: [], edges: [], tiles: []};
		o.draw = 'p3_vert'; o.p1 = 'diamond';
		p3_verts[k] = o;
		o.verts.push(find_intersection(b[0], b[1]));
		o.verts.push(find_intersection(b[1], b[2]));
		o.verts.push(find_intersection(b[2], b[3]));
		o.verts.push(find_intersection(b[3], b[4]));
		o.verts.push(find_intersection(b[4], b[5]));
		o.verts.push(find_intersection(b[5], b[6]));
		o.verts.push(find_intersection(b[6], b[0]));

		o.vert = get_intersection(
		get_bar(
			o.verts[0].x, o.verts[0].y,
			(Math.PI + b[0].theta + b[1].theta) / 2
		),
		get_bar(
			o.verts[1].x, o.verts[1].y,
			(Math.PI + b[1].theta + b[2].theta) / 2
		)
		);
		o.x = o.vert.x;
		o.y = o.vert.y;
		o.color = find_color(b);
		o.t = true;

		set_fill_style("gray");
		//fill_polygon(scale_points(o.verts));
		//draw_quick_dot(o, 1, "gray");
		fresh_tiles.push(o);
	}
	return o;
}

function find_boat_p3_vert(b) {
	var k = [];
	for (var i in b) {k.push(b[i].k)}
	k.sort();
	k = "boat|" + k.join("|");
	var o = p3_verts[k];
	if (null == o) {
		o = {k: k, bars: b, verts: [], edges: [], tiles: []};
		o.draw = 'p3_vert'; o.p1 = 'boat';
		p3_verts[k] = o;
		o.verts.push(find_intersection(b[0], b[1]));
		o.verts.push(find_intersection(b[1], b[2]));
		o.verts.push(find_intersection(b[2], b[3]));
		o.verts.push(find_intersection(b[3], b[4]));
		o.verts.push(find_intersection(b[4], b[5]));
		o.verts.push(find_intersection(b[5], b[0]));

		o.vert = get_intersection(
		get_bar(
			o.verts[0].x, o.verts[0].y,
			(Math.PI + b[0].theta + b[1].theta) / 2
		),
		get_bar(
			o.verts[1].x, o.verts[1].y,
			(Math.PI + b[1].theta + b[2].theta) / 2
		)
		);
		o.x = o.vert.x;
		o.y = o.vert.y;
		o.color = find_color(b);
		o.t = true;

		set_fill_style("gray");
		//fill_polygon(scale_points(o.verts));
		//draw_quick_dot(o, 1, "gray");
		fresh_tiles.push(o);
	}
	return o;
}

function find_sun_p3_vert(b) { 
	var k = [];
	for (var i in b) {k.push(b[i].k)}
	k.sort();
	k = "sun|" + k.join("|");
	var o = p3_verts[k];
	if (null == o) {
		o = {k: k, bars: b, verts: [], edges: [], tiles: []};
		o.draw = 'p3_vert'; o.p1 = 'sun';
		p3_verts[k] = o;
		o.verts.push(find_intersection(b[0], b[1]));
		o.verts.push(find_intersection(b[1], b[2]));
		o.verts.push(find_intersection(b[2], b[3]));
		o.verts.push(find_intersection(b[3], b[4]));
		o.verts.push(find_intersection(b[4], b[0]));

		o.vert = get_polygon_center(o.verts);
		o.x = o.vert.x;
		o.y = o.vert.y;
		o.color = find_color(b);

		set_fill_style("gray");
		//fill_polygon(scale_points(o.verts));
		//draw_quick_dot(o, 1, "gray");
		fresh_tiles.push(o);
	}
	return o;
}

function find_star_p3_vert(b) { 
	var k = [];
	for (var i in b) {k.push(b[i].k)}
	k.sort();
	k = "star|" + k.join("|");
	var o = p3_verts[k];
	if (null == o) {
		o = {k: k, bars: b, verts: [], edges: [], tiles: []};
		o.draw = 'p3_vert'; o.p1 = 'star';
		p3_verts[k] = o;
		o.verts.push(find_intersection(b[0], b[1]));
		o.verts.push(find_intersection(b[1], b[2]));
		o.verts.push(find_intersection(b[2], b[3]));
		o.verts.push(find_intersection(b[3], b[4]));
		o.verts.push(find_intersection(b[4], b[0]));

		o.vert = get_polygon_center(o.verts);
		o.x = o.vert.x;
		o.y = o.vert.y;
		o.color = find_color(b);
		o.t = true;

		set_fill_style("gray");
		//fill_polygon(scale_points(o.verts));
		//draw_quick_dot(o, 1, "gray");
		fresh_tiles.push(o);
	}
	return o;
}

function find_cartwheel_star(c) {
	var b = c.obars;
	var sb = find_next_bar(b[2][1], b[2][2]);
	var asb = [
		b[0][0],
		b[1][0],
		sb,
		b[3][0],
		b[4][0]
	];
	var sv = find_star_p3_vert(asb);
	c.p3_verts['star'] = sv;

	var ob = [
		find_next_bar(b[0][1], b[0][0]),
		b[1][1],
		b[2][2],
		b[3][1],
		find_next_bar(b[4][1], b[4][0])
	];

	find_p3_edge([sv, find_deuce_p3_vert([asb[2], asb[3], ob[0]])]);
	find_p3_edge([sv, find_deuce_p3_vert([asb[3], asb[4], ob[1]])]);
	find_p3_edge([sv, find_deuce_p3_vert([asb[4], asb[0], ob[2]])]);
	find_p3_edge([sv, find_deuce_p3_vert([asb[0], asb[1], ob[3]])]);
	find_p3_edge([sv, find_deuce_p3_vert([asb[1], asb[2], ob[4]])]);

}

function draw_cartwheel_decagon(c) {
	set_line_width(1);
	var clr = [];
	for (var i in c.color) { clr[i] = Math.floor(c.color[i] / 4); }
	clr = 'rgb(' + clr.join(',') + ')';
	set_stroke_style(clr);
	draw_polygon(scale_points(c.verts));
	//draw_segments(scale_points([c.verts[0], c.vert]));
}

function draw_quick_decagon(d, r, clr, a) {
	var c = clr;
	if (opts['cartwheel']) { draw_quick_dot(d, r, c); }
	if (! opts['decagon']) { return; }
	if (null != a) {
		c = 'rgb(' + a.join(',') + ')';
	}
	var p = scale_points([d])[0];

	set_stroke_style((null == c) ? "white" : c);
	var nw = (opts['edges'] ? 3 : opts['colors'] ? 2 : 1);
	var nr = r - nw;
	if (thickness > 1) { nr -= 2 * thickness; }
	if (nr < 0) {nr = 0;}
	set_line_width(nw);

	p = get_regular_polygon_points(10, p.x, p.y, nr, Math.PI / 2);
	draw_polygon(p);
	/*
	if (null != c) {
		set_fill_style(c);
		fill_polygon(p);
	}
	*/
}

function draw_cartwheel(c) {

	//draw_cartwheel_decagon(c);
	var m = (opts['cartwheel'] ? 1 : .5);
	if (opts['bars']) {
		for (var i in c.bars) {
			draw_bars(c.bars[i], opts["cartwheel"] ? .5 : null);
		}
	}
	if (! (opts['cartwheel'] || opts['decagon'])) { return; }
	var clr = [];
	for (var i in c.color) { clr[i] = Math.floor(c.color[i] / (2 * m)); }
	clr = 'rgb(' + clr.join(',') + ')';

	//draw_quick_dot(c.vert, 2, clr);

	clr = [];
	for (var i in c.color) { clr[i] = Math.floor(c.color[i] / 
	((opts['p2'] || opts['p3']) ? (16 * m) : 
	(opts['bars'] ? (8 * m) : (4 * m)))
	); }
	clr = 'rgb(' + clr.join(',') + ')';

	set_fill_style(clr);
	//fill_polygon(scale_points(c.decagon, clr));

	var p = scale_points([c.vert, 
	c.p3_verts['boat'], 
	c.p3_verts['star'], 
	c.p3_verts['ots'],
	c.p3_verts['itp']
	]);
	if (opts['p2'] || (!(opts['p2'] || opts['p3']))) {
		var a = [];
		for (var i in c.color) {
			a.push(Math.floor(c.color[i]/2));
		}
		draw_quick_decagon(c.vert, get_distance(p[0], p[2]), clr, a);
	}
	if (opts['p3']) {
	if (! opts['p2']) {
		clr = [];
		for (var i in c.color) { clr[i] = Math.floor(c.color[i] / (8 * m)); }
		clr = 'rgb(' + clr.join(',') + ')';
	}
	draw_quick_decagon(c.vert, get_distance(p[0], p[3]), clr);
	}
	//draw_quick_dot(c.vert, get_distance(p[0], p[1]), clr);
	if (opts['p2'] || opts['p3']) {
	clr = [];
	for (var i in c.color) { clr[i] = Math.floor(c.color[i] / (16 * m)); }
	clr = 'rgb(' + clr.join(',') + ')';
	draw_quick_decagon(c.p3_verts["star"], get_distance(p[1], p[2]), clr);
	}
	if (opts['p2']) {
	clr = [];
	for (var i in c.color) { clr[i] = Math.floor(c.color[i] / (16 * m)); }
	clr = 'rgb(' + clr.join(',') + ')';
	draw_quick_decagon(c.p3_verts["ots"], get_distance(p[3], p[4]), clr);
	draw_quick_decagon(c.p3_verts["ols"], get_distance(p[3], p[4]), clr);
	draw_quick_decagon(c.p3_verts["ors"], get_distance(p[3], p[4]), clr);
	}

}

function cartwheel_inside(c) {
return null != c && ((
	(c.vert.x + cartwheel_r > v.l[0].x) &&
	(c.vert.x - cartwheel_r < v.r[0].x) &&
	(c.vert.y + cartwheel_r > v.t[0].y) &&
	(c.vert.y - cartwheel_r < v.b[0].y)
	) || (
	(c.p3_verts['star'].x + star_r > v.l[0].x) &&
	(c.p3_verts['star'].x - star_r < v.r[0].x) &&
	(c.p3_verts['star'].y + star_r > v.t[0].y) &&
	(c.p3_verts['star'].y - star_r < v.b[0].y)
	));
}

function find_tile_color(t) {
	var clr = [0, 0, 0];
	var n = 0;
	for (var i in t.verts) {
		if (null == t.verts[i].color) { continue; }
		++n;
		for (var j in t.verts[i].color) {
			clr[j] += t.verts[i].color[j];
		}
	}
	if (n > 0) {
		for (var i in clr) {
			clr[i] = Math.floor(clr[i] / n);
		}
	}
	t.color = clr;
}

function find_color(b) {
	var o = [0, 0, 0];
	var n = 0;
	for (var i in b) {
		if (null == b[i].color) {continue;}
		++n;
		for (var j in b[i].color) {
			o[j] += b[i].color[j];
		}
	}

	for (var i in o) {
		o[i] = Math.floor(o[i] / n);
	}

	return o;
}

function find_p3_tile(v0, v1, v2, v3) {
	var k = [v0.k, v2.k];
	k = 'p3:[' + k.join('][') + ']';
	if (null == p3_tiles[k]) {
		var o = {k: k, verts: [v0, v1, v2, v3], edges: []}
		o.draw = 'p3_tile';
		o.color = find_color([v0, v1, v2, v3]);
		for (var i = 0; i < 4; ++i) {
			var va = o.verts[i];
			var vb = o.verts[(i + 1) % 4];
			var e = find_p3_edge([va, vb]);
			if (null == e) { return null; }
			e.tiles[k] = o;
			o.edges[e.k] = e;
		}
		if (null == long_d) {
			long_d = get_distance(o.verts[0], o.verts[1]);
		}
		o.t = get_distance(o.verts[0], o.verts[2]) < long_d;
		if (o.t && null == arc_d) {
			arc_d = get_distance(o.verts[0], o.verts[2]) / 2;
		}
		find_p3_dart_vert(o);
		find_p3_p1_vert(o);
		find_tile_color(o);
		p3_tiles[k] = o;
		fresh_tiles.push(o);
		/*
		set_stroke_style("rgb(63,63,63)");
		draw_polygon(scale_points(o.verts));
		set_fill_style("rgb(15,15,15)");
		fill_polygon(scale_points(o.verts));
		*/
	}
	return p3_tiles[k];
}

function find_p3_p1_vert(t) {
	if (! opts['p1']) {return;}
	console_log("draw_p1_verts_begin " + t.k);
	var clr = [0, 0, 0];
	if (null == t.color) { return; }
	for (var i in t.color) {clr[i] = t.color[i];}
	// set the color of the p1 edges inside this p3 tile

	// to find the p1 vert
	var m1 = get_midpoint(t.verts[1], t.verts[2]);
	var t1 = get_theta([t.verts[1], t.verts[2]]) + Math.PI / 2;
	var b1 = [m1, {
		x: m1.x - Math.cos(t1),
		y: m1.y + Math.sin(t1)
	}];

	var m2 = get_midpoint(t.verts[3], t.verts[2]);
	var t2 = get_theta([t.verts[3], t.verts[2]]) + Math.PI / 2;
	var b2 = [m2, {
		x: m2.x - Math.cos(t2),
		y: m2.y + Math.sin(t2)
	}];

	var e1 = find_p3_edge([t.verts[0], t.verts[1]]);
	var e2 = find_p3_edge([t.verts[0], t.verts[3]]);

	if (opts['p2'] || opts['p3']) {
		// only dim the color if we're also showing p2 or p3 tiles
		for (var i in clr) { clr[i] = Math.floor(clr[i] / 2); }
		set_line_width(1);
	} else {
		if (opts['bars'] || opts['cartwheel'] || opts['rings']) {
			set_line_width(1);
		} else {
			set_line_width(2);
		}
	}

	clr = 'rgb(' + clr.join(',') + ')';
	set_stroke_style(clr);

	var b, bb;

	if (! t.t) {
		t.p1_vert = get_intersection(b1, b2);
		// DRAW P1 TILES HERE
		// some p1 edge segments get drawn here.  this is kind of a hack
		draw_segments(scale_points([m1, t.p1_vert]));
		draw_segments(scale_points([t.p1_vert, m2]));
		var th = Math.PI + get_theta([t.p1_vert, t.verts[2]]);

		var m = -1;

		var a = [e1, e2];
		for (var k in a) {
			if (null == a[k].p1_vert) {
				var e = a[k];
				b = [];
				for (var i in e.verts) {b.push(e.verts[i])}
				var n = 2;
				while (n > 0) {
				--n;
				bb = [t.p1_vert, {
					x: t.p1_vert.x - Math.cos(th + m * Math.PI / 10),
					y: t.p1_vert.y + Math.sin(th + m * Math.PI / 10)
				}];
				e.p1_vert = get_intersection(b, bb);
				m = -m;
				if (get_distance(e.p1_vert, t.p1_vert) < 1) { break; }
				}
			}
		}
		// DRAW P1 TILES HERE TOO
		// this is where the p1 tiles get drawn more
		draw_segments(scale_points([e1.p1_vert, t.p1_vert]));
		draw_segments(scale_points([t.p1_vert, e2.p1_vert]));
	} else {
		if (null == e1.p1_vert) {
			b = [];
			for (var i in e1.verts) {b.push(e1.verts[i])}
			e1.p1_vert = get_intersection(b, b1);
		}
		if (null == e2.p1_vert) {
			b = [];
			for (var i in e2.verts) {b.push(e2.verts[i])}
			e2.p1_vert = get_intersection(b, b2);
		}
		// DRAW P1 TILES HERE TOO
		// this is where the p1 segments get drawn even more
		draw_segments(scale_points([m1, e1.p1_vert]));
		draw_segments(scale_points([e1.p1_vert, e2.p1_vert]));
		draw_segments(scale_points([e2.p1_vert, m2]));
	}
	console_log("draw_p1_verts_end " + t.k);

}

function find_p3_dart_vert(t) {
	if (t.t) { return; }
	if (null == long_d) {
		long_d = get_distance(t.verts[0], t.verts[1]);
	}
	t.dart_vert = get_midpoint_d(long_d, t.verts[2], t.verts[0]);
	t.dart_vert.k = t.dart_vert.x + ',' + t.dart_vert.y;
	t.dart_vert.t = true;

	/*
	var dot = new_draw_dot();
	dot.clr = "gray";
	dot.r = 1;

	var p = scale_points([t.dart_vert])[0];
	dot.x = p.x;
	dot.y = p.y;
	draw_dot(dot);
	*/
}

function find_p3_tiles(vert) {
	//find all p3 tiles surrounding this vert.
	// follow all edges surrounding this vert out two edges
	// all pairs of paths that have a common vert at the end
	// form a tile

	var o = [];
	if (! vert.t) {return;}
	p3_verts_found[vert.k] = true;
	var t = [];
	for (var i0 in vert.edges) {
		var e0 = vert.edges[i0];
		for (var j0 in e0.verts) {
			var v1 = e0.verts[j0];
			if (p3_verts_found[v1.k]) { continue; }
			for (var i1 in v1.edges) {
				var e1 = v1.edges[i1];
				if (e1.k == e0.k) { continue; }
				for (var j1 in e1.verts) {
					var v2 = e1.verts[j1];
					if (v2.k == v1.k) { continue; }
					if (p3_verts_found[v2.k]) { continue; }
					if (null == t[v2.k]) { 
						t[v2.k] = []; 
					}
					t[v2.k][v1.k] = [v1, v2]; 
				}
			}
		}
	}

	for (var i in t) {
		var a = [];
		for (var j in t[i]) {
			a.push(t[i][j]);
		}
		if (2 != a.length) { continue; }
		o.push(find_p3_tile(vert, a[0][0], a[0][1], a[1][0]));
	}
}

function find_cartwheel_p3_tiles(c) {
	find_p3_tiles(c.p3_verts['boat']);
	find_p3_tiles(c.p3_verts['ldi']);
	find_p3_tiles(c.p3_verts['rdi']);
	find_p3_tiles(c.p3_verts['star']);
}

function cartwheel_sort(a, b) {
	var ad = get_distance(a.vert, v);
	var bd = get_distance(a.vert, v);
	if (ad < bd) { return -1; }
	if (ad > bd) { return 1; }
	return 0;
}

function expand_cartwheels() {
	console_log("expand");
	var a = p3_retry;
	p3_retry = [];
	for (var i in a) { find_p2_tiles(a[i]); }
	while (fresh_tiles.length > 0) {
		var nt = fresh_tiles.shift();
		if ('p3_tile' == nt.draw) { 
			find_p2_tiles(nt);
		}
		draw_tiles.push(nt);
	}

	var dn = Math.floor(Math.random() * 32);
	while (draw_tiles.length > 0 && dn > 0) {
		--dn;
		var tmp = draw_tiles[0];
		var tn = Math.floor(Math.random() * draw_tiles.length);
		draw_tiles[0] = draw_tiles[tn];
		draw_tiles[tn] = tmp;
		draw_thing(draw_tiles.shift());
	}

	if (0 == fresh_cartwheels.length && 0 == draw_tiles.length) { 
		while (draw_tiles.length > 0) {
			draw_thing(draw_tiles.shift());
		}
		done_drawing();
		return; 
	}

	var n = 1;
	while (n > 0 && fresh_cartwheels.length > 0) {
		--n;

		fresh_cartwheels.sort(cartwheel_sort);
		var rn = 1;
		for (var i = 0; i < 3; ++i) { rn *= Math.random(); }

		var cn = Math.floor(rn * fresh_cartwheels.length);
		var c = fresh_cartwheels[cn];
		fresh_cartwheels[cn] = fresh_cartwheels[0];
		fresh_cartwheels.shift();
		var d = get_distance(c.vert, v);
		if (d < seed_d) { seed_d = d; seed_a = c.a; }
		if (cartwheel_inside(c)) { 
			find_tangent_cartwheels(c); 
			find_cartwheel_p3_tiles(c);
			/*
			for (var i in c.tangents) {
				if (null == c.tangents[i]) { continue; }
				find_tangent_cartwheels(c.tangents[i]);
				find_cartwheel_p3_tiles(c.tangents[i]);
			}
			*/
		}
	}
	expand_cartwheels();
}

function draw_p3_arcs(t) {
	if (! opts['arcs']) { return; }
	var th = get_theta([t.verts[2], t.verts[0]]);

	th += Math.PI;
	th = - th;

	var p = scale_points([t.verts[0]])[0];
	var d = arc_d;
	var dth = ((t.t ? 2 : 1 ) * Math.PI / 5);

	set_line_width(1);
	set_stroke_style(opts['p2'] ? "rgb(31,31,31)" : "rgb(63,63,63)");

	ctx.save();
	ctx.beginPath();
	ctx.arc(p.x, p.y, d * v.z, th - dth, th + dth, false);
	ctx.stroke();
	ctx.restore();

	d = (t.t ? arc_d : long_d - arc_d);
	th += Math.PI;
	var p = scale_points([t.verts[2]])[0];
	set_stroke_style(opts['p2'] ? "rgb(63, 63, 63)" : "rgb(127,127,127)");

	ctx.save();
	ctx.beginPath();
	ctx.arc(p.x, p.y, d * v.z, th - dth, th + dth, false);
	ctx.stroke();
	ctx.restore();

}

function fill_tile(t, c) {
	if (! opts['colors']) { return; }
	var m = c;
	if (null == m) { 
		m = 1/2;
		if (opts['cartwheel']) { m /= phi; }
		if (opts['bars']) { m /= phi; }
		if (opts['edges']) { m /= phi; }
		if (opts['p2'] && opts['p3']) { m /= phi; }
	}

	clr = [0, 0, 0];
	if (null == t.color) { return; }
	for (var i in t.color) {clr[i] = t.color[i];}

	for (var i in clr) { clr[i] = Math.floor(clr[i] * m); }
	clr = 'rgb(' + clr.join(',') + ')';
	set_fill_style(clr);
	fill_polygon(scale_points(t.verts));
}

function draw_tile_edge(e, clr) {
	error_log("draw_tile_edge", e);
	if (null != drawn[e.k]) { return; }
	//if (! opts['edges']) { return; }
	drawn[e.k] = true;

	var c = clr;
	var u = null;
	if (null == c) { 
		u = Math.floor( 256 / (phi * phi));
		c = 'rgb('+ u + ',' + u + ','+ u + ')';
	}
	if (! opts['colors']) { 
		u = Math.floor(256 / phi); 
		c = 'rgb('+ u + ',' + u + ','+ u + ')';
	}
	set_stroke_style(c);
	set_line_width(1);
	var a = [];
	for (var i in e.verts) {a.push(e.verts[i]);}
	draw_segments(scale_points(a));
}

function draw_p3_tile(t) {
	if (! opts['p3']) { return; }
	if (opts['edges'] || (!(
		opts['arcs'] || 
		opts['cartwheel'] || 
		opts['decagon'] || 
		opts['colors'] ||
		opts['edges']
	))) { 
		for (var i in t.edges) { draw_tile_edge(t.edges[i], (opts['p2'] ? "rgb(31,31,31)" : null)); }
		}
	fill_tile(t);
	draw_p3_arcs(t);
}

function draw_p1_tile(t) {
	// not implemented;
	// p1 edges are drawn when cartwheels are found
	// search code for "DRAW P1 TILE"
	// it is in the function find_p3_p1_vert
}

function draw_thing(t) {
	var f = t.draw;
	console_log("draw_thing_begin " + f + " " + t.k);
	if (null == f) { return; }
	else if ('p1_tile' == f) { draw_p1_tile(t); }
	else if ('p3_tile' == f) { draw_p3_tile(t); }
	else if ('p2_tile' == f) { draw_p2_tile(t); }
	else if ('cartwheel' == f) { draw_cartwheel(t); }
	else if ('p3_vert' == f) { draw_ring(t); }
	console_log("draw_thing_end " + f + " " + t.k);
}

function draw_ring(t) {
	if (! opts['rings']) { return; }
	var d =  p3_l / 2;
	var m = 4;
	var w = true;
	if (
		'diamond' == t.p1 ||
		'boat' == t.p1 ||
		'star' == t.p1 
	) { d *= phi; m = 2; w = true}

	// brighten if we are not drawing p2 or p3 tiles
	if (! (opts['p2'] || opts['p3'])) { m /= 2; }
	if (opts['arcs'] && ! (opts['p1'] || opts['edges'] || opts['colors'] || opts['cartwheel'])) { m /= 2; }

	var clr = [];
	for (var i in t.color) { clr[i] = Math.floor(t.color[i] / m); }
	clr = 'rgb(' + clr.join(',') + ')';
	set_stroke_style(clr);
	set_fill_style(clr);

	set_line_width(1);
	ctx.save();
	ctx.beginPath();
	var p = scale_points([t])[0];
	ctx.arc(p.x, p.y, d * v.z, 0, 2 * Math.PI, false);
	w ? ctx.stroke() : ctx.fill();
	ctx.restore();

}

function init_tangents() {
	var t = [
		[-1, -1, 2, 0, -2],
		[0, -2, 1, 1, -2],
		[1, -2, 0, 2, -1],
		[2, -1, -1, 2, 0],
		[2, 0, -2, 1, 1],
		[1, 1, -2, 0, 2],
		[0, 2, -1, -1, 2],
		[-1, 2, 0, -2, 1],
		[-2, 1, 1, -2, 0],
		[-2, 0, 2, -1, -1]
	];
	for (var i = 0; i < 10; ++i) {
		t[i + 10] = [];
		for (var j = 0; j < 5; ++j) {
			t[i + 10][j] = t[i][j];
			if (t[i][j] > 0) { ++t[i + 10][j]; }
			else if (t[i][j] < 0) { --t[i + 10][j]; }
		}
	}
	tangents = t;
}

function find_tangent_cartwheels(c) {
	if (null == c) { return []; }
	if (null != c.tangents) { return c.tangents; }
	var t = tangents;
	var r = [];
	var o = [];
	for (var i in t) {
		var a = [];
		for (var j in c.a) {
			a[j] = c.a[j] + t[i][j];
		}
		o[i] = find_cartwheel(a);
	}
	c.tangents = o;
	return o;
}

function find_seed_cartwheel() {
	if (null == seed) { 
		if (null == seed_a) { 
			seed_a = [0,0,0,0,0]; 
		}
		seed = find_cartwheel(seed_a); 
		seed_d = get_distance(seed.vert, v);
	}
	if (cartwheel_inside(seed)) { return expand_cartwheels(); }
	fresh_cartwheels = [];
	var d = get_distance(seed.vert, v);
	find_tangent_cartwheels(seed);
	while (fresh_cartwheels.length > 0) {
		var c = fresh_cartwheels.shift();
		var cd = get_distance(c.vert, v);
		if (cd < d) {
			seed = c;
			seed_a = c.a;
			d = cd;
			seed_d = cd;
		}
	}
	fresh_cartwheels = [seed];
	find_seed_cartwheel();
}

function get_theta(b) {
	var dx = b[1].x - b[0].x;
	var dy = b[1].y - b[0].y;
	return Math.atan2(-dy, dx);
}

function find_cartwheels() {
	error_log("find cartwheels")
	//draw_cartwheel(find_cartwheel([0, 0, 0, 0, 0]));
	find_seed_cartwheel(10);
}

function redraw() {
	console_log("redraw " + canvas.width + " " + canvas.height);
	is_drawing = true;
	console_clear();
	canvas_clear();
	clear_globals();
	find_cartwheels();

	set_stroke_style("white");
	set_line_width(1);

	/*
	for (var i in cartwheels) { 
		draw_cartwheel(cartwheels[i]);
	}
	*/
	//draw_bars(bars, .5);
}

function get_midpoint(p1, p2) {
	var o = new Object();
	o.x = (p1.x + p2.x) / 2;
	o.y = (p1.y + p2.y) / 2;
	return o;
}

function get_polygon_center(p) {
	var a = [];
	for (var i in p) { a.push(p[i]); }
	var o = {x: 0, y:0};
	for (var i in a) {
		o.x += a[i].x / a.length;
		o.y += a[i].y / a.length;
	}
	return o;
}

function get_midpoint_r(r, p1, p2) {
	var dx = (p2.x - p1.x);
	var dy = (p2.y - p1.y);
	var d = Math.sqrt(dx * dx + dy * dy);
	var th = Math.atan2(dy, dx);
	var o = new Object();
	o.x = p2.x + r * d * Math.cos(th);
	o.y = p2.y + r * d * Math.sin(th);
	return o;
}

function get_midpoint_d(d, p1, p2) {
	var dx = (p2.x - p1.x);
	var dy = (p2.y - p1.y);
	var th = Math.atan2(dy, dx);
	var o = new Object();
	o.x = p1.x + d * Math.cos(th);
	o.y = p1.y + d * Math.sin(th);
	return o;
}

function get_intersection(a, b) {
	//log_bar("a", a);
	//log_bar("b", b);
	var p1 = a[0];
	var p2 = a[1];
	var p3 = b[0];
	var p4 = b[1];

	// p1 and p2 are on one line,
	// p3 and p4 are on the other
	
	var d = (
		(p4.y - p3.y) * (p2.x - p1.x) -
		(p4.x - p3.x) * (p2.y - p1.y)
	);
	if (0 == d) { return null; }

	var u = (
		(p4.x - p3.x) * (p1.y - p3.y) -
		(p4.y - p3.y) * (p1.x - p3.x)
	) / d;

	var o = new Object();
	o.x = p1.x + u * (p2.x - p1.x);
	o.y = p1.y + u * (p2.y - p1.y);

	o.k = ('(' + o.x + ',' + o.y + ')');
	//log_bar("intersects", [o]);
	return o;
}

function get_distance(p1, p2) {
	var dx = (p2.x - p1.x);
	var dy = (p2.y - p1.y);
	return Math.sqrt(dx * dx + dy * dy);
}

function get_regular_polygon_points(sides, x, y, r, theta) {
	var p = new Array();
	var th;
	var i;
	for (i = 0; i < sides; ++i) {
		th = theta + i * 2 * Math.PI / sides;
		p[i] = new Object();
		p[i].x = x + r * Math.cos(th);
		p[i].y = y - r * Math.sin(th);
	}
	return p;
}

function scale_points(p) {
	var o = new Array();
	for (var i in p) {
		o[i] = {
			x: p[i].x * v.z + v.cx - v.x * v.z, 
			y: p[i].y * v.z + v.cy - v.y * v.z
		}
	}
	return o;
}

function find_parallel_line(p, r) {

	var dx = (p[0].x - p[1].x);
	var dy = (p[0].y - p[1].y);

	var th = Math.atan2(dy, dx) - Math.PI / 2;

	var o = new Array();
	for (var i  in p) {
		o[i] = {x: p[i].x, y: p[i].y};
		o[i].x += r * Math.cos(th);
		o[i].y += r * Math.sin(th);
	}

	return o;
}

function fill_polygon(p) {
	ctx.save();
	ctx.beginPath();
	ctx.moveTo(p[0].x, p[0].y);
	for (i = 1; i < p.length; ++i) {
		ctx.lineTo(p[i].x, p[i].y);
	}
	ctx.closePath();
	ctx.fill();
	ctx.restore();
	return p;
}

function draw_polygon(p) {
	ctx.save();
	ctx.beginPath();
	ctx.moveTo(p[0].x, p[0].y);
	for (i = 1; i < p.length; ++i) {
		ctx.lineTo(p[i].x, p[i].y);
	}
	ctx.closePath();
	ctx.stroke();
	ctx.restore();
	return p;
}

function draw_bars(b, n) {
	for (var k in b) {
		if (null != drawn[b[k].k]) { continue; } 
		drawn[b[k].k] = true;
		var bar = b[k];
		var clr = rainbow_color(.5 + bar.d / 12);
		if (opts['gray']) { clr = 'white'; }
		set_stroke_style(clr);
		set_line_width(1);
		if (null != n) {set_line_width(n)};
		draw_bar(bar.bar);
		set_line_width(1.0);
	}
}

function draw_segments(b) {
	//log_bar("draw", b);
	ctx.save();
	ctx.beginPath();
	ctx.moveTo(b[0].x, b[0].y);
	for (var i = 1; i < b.length; ++i) {
		ctx.lineTo(b[i].x, b[i].y);
		//log_bar("draw", b);
	}
	ctx.stroke();
	ctx.restore();
}

function draw_bar(s) {
	var b = find_view_bar(s);
	if (null == b) {return; }
	b = scale_points(b);
	draw_segments(b);
}

function find_view_bar(s) {
	var o = new Array();
	o.push(get_intersection(s, v.l));
	o.push(get_intersection(s, v.t));
	o.push(get_intersection(s, v.r));
	o.push(get_intersection(s, v.b));

	// remove all points that are outside the viewport

	var b = new Array();
	var p;
	while (o.length > 0) {
		p = o.shift();
		if (null == p) { continue; }
		if (p.x < v.l[0].x - .00001) { continue; }
		if (p.x > v.r[0].x + .00001) { continue; }
		if (p.y < v.t[0].y - .00001) { continue; }
		if (p.y > v.b[0].y + .00001) { continue; }
		b.push(p);
		if (2 == b.length) {break; }
	}
	//log_bar("scaled", b);
	if (2 == b.length) {return b;}
	return null;
}

function init_options() {
	opts = {
		bars: false,
		rings: false,
		p1: false,
		p2: false,
		p3: false,
		cartwheel: false,
		arcs: false,
		edges: false,
		colors: false,
		decagon: false,
		gray: false
	};
	opt_labels = {
		bars: 'Bars',
		rings: 'Rings',
		p1: 'Pentagons',
		p2: 'Kites &amp; Darts',
		p3: 'Rhombs',
		cartwheel: 'Discs',
		arcs: 'Arcs',
		edges: 'Edges',
		colors: 'Colors'
	};
	var q = false;

	for (var i in opts) {
		if (null != query[i]) {
			opts[i] = true;
			q = true;
		}
	}
	var a = ['x', 'y', 'z'];
	for (var i in a) {
		if (null != query[a[i]]) {
			v[a[i]] = query[a[i]] - 0;
			q = true;
		}
	}

	if (q) {
		seed_a = [];
		for (var i = 0; i < 5; ++i) {
			seed_a[i] = query['s' + i] - 0;
			if (isNaN(seed_a[i])) {
				seed_a[i] = 0;
			}
		}
		auto_update = false;
	} else {
		auto_update = true;
		random_options();
	}
}

function share_opts() {
	var s = [];
	for (var i in opts) {
		if (opts[i]) { s.push(i + '=1'); }
	}
	s.push('x=' + v.x);
	s.push('y=' + v.y);
	s.push('z=' + v.z);
	for (var i in seed_a) {
		s.push('s' + i + '=' + seed_a[i]);
	}
	s.push('patch=cartwheel-tiling');
	return s.join('&');
}

function share_tiling() {
	var u = '../share/share.html?' + share_opts();
	document.location = u;
}

function done_drawing() {
	is_drawing = false;
	//if (auto_update) {
	//	auto_timeout = setTimeout('random_redraw()', 5000);
	//}

	console_log("done_drawing");
}

function nothing_shows() {
	return (
		(!(opts['p2'] || opts['p3'])) && (!(
			opts['p1'] || 
			opts['bars'] || 
			opts['cartwheel'] ||
			opts['decagon'] ||
			opts['rings']
		))
	);
}

function random_options() {
	do {
		for (var i in opts) {
			opts[i] = (.5 > Math.random());
		}
	} while (nothing_shows());
	if (.5 > Math.random()) { opts['gray'] = false; }
}

function random_redraw() {

	random_options();

	if (.5 > Math.random()) { return redraw(); }
	if (.5 > Math.random()) { return view_scroll(
		(2 - 4 * Math.random()),
		(2 - 4 * Math.random())
	); }
	if (.5 > Math.random()) {
		return view_zoom(1 - Math.random() * 2);
	}
	return redraw();
}

init();
